﻿using System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Diagnostics;

namespace CodeCounsel.SharePoint.RibbonDesigner.View.AttachedBehaviors
{
	public class DragDropHelper
	{
        public static readonly DependencyProperty DragProcessorProperty = 
            DependencyProperty.RegisterAttached("DragProcessor", typeof(IDragProcessor), typeof(DragDropHelper));


        public static readonly DependencyProperty DropProcessorProperty =
            DependencyProperty.RegisterAttached("DropProcessor", typeof(IDropProcessor), typeof(DragDropHelper));

        public static readonly DependencyProperty IsDragSourceProperty =
            DependencyProperty.RegisterAttached("IsDragSource", typeof(bool), typeof(DragDropHelper),
                new UIPropertyMetadata(false, IsDragSourceChanged));

        public static readonly DependencyProperty IsDropTargetProperty =
            DependencyProperty.RegisterAttached("IsDropTarget", typeof(bool), typeof(DragDropHelper),
                new UIPropertyMetadata(false, IsDropTargetChanged));

        public static readonly DependencyProperty DragDropTemplateProperty =
            DependencyProperty.RegisterAttached("DragDropTemplate", typeof(DataTemplate), typeof(DragDropHelper), 
                new UIPropertyMetadata(null));

        static DragDropHelper _instance;
        static DataFormat _format;

		Point _initialMousePosition;
		Vector _initialMouseOffset;
		Window _topWindow;

        DragManager _sourceManager;
        DropManager _targetManager;

        static DragDropHelper Instance 
		{
			get 
			{  
				if(_instance == null)
				{
					_instance = new DragDropHelper();
				}
				return _instance;
			}
		}

        static DragDropHelper()
        {
            _format = DataFormats.GetDataFormat("RibbonDragDrop");
        }

        #region Dependency Properties
        public static IDragProcessor GetDragProcessor(DependencyObject obj)
        {
            return (IDragProcessor)obj.GetValue(DragProcessorProperty);
        }

        public static void SetDragProcessor(DependencyObject obj, IDragProcessor value)
        {
            obj.SetValue(DragProcessorProperty, value);
        }

        public static IDropProcessor GetDropProcessor(DependencyObject obj)
        {
            return (IDropProcessor)obj.GetValue(DropProcessorProperty);
        }

        public static void SetDropProcessor(DependencyObject obj, IDropProcessor value)
        {
            obj.SetValue(DropProcessorProperty, value);
        }

		public static bool GetIsDragSource(DependencyObject obj)
		{
			return (bool)obj.GetValue(IsDragSourceProperty);
		}

		public static void SetIsDragSource(DependencyObject obj, bool value)
		{
			obj.SetValue(IsDragSourceProperty, value);
		}

		public static bool GetIsDropTarget(DependencyObject obj)
		{
			return (bool)obj.GetValue(IsDropTargetProperty);
		}

		public static void SetIsDropTarget(DependencyObject obj, bool value)
		{
			obj.SetValue(IsDropTargetProperty, value);
		}

		public static DataTemplate GetDragDropTemplate(DependencyObject obj)
		{
			return (DataTemplate)obj.GetValue(DragDropTemplateProperty);
		}

		public static void SetDragDropTemplate(DependencyObject obj, DataTemplate value)
		{
			obj.SetValue(DragDropTemplateProperty, value);
		}
        #endregion

        #region Hookup
        static void IsDragSourceChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            if (DragDropManagerFactory.IsSupportedDragSource(obj))
            {
                FrameworkElement dragSource = (FrameworkElement)obj;
                if ((bool)e.NewValue == true)
                {
                    Instance.AttachDragSource(dragSource);
                }
                else
                {
                    Instance.DetachDragSource(dragSource);
                }
            }
        }

        static void IsDropTargetChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            if (DragDropManagerFactory.IsSupportedDropTarget(obj))
            {
                FrameworkElement dropTarget = (FrameworkElement)obj;
                if ((bool)e.NewValue == true)
                {
                    dropTarget.AllowDrop = true;
                    Instance.AttachDropTarget(dropTarget);
                }
                else
                {
                    dropTarget.AllowDrop = false;
                    Instance.DetachDropTarget(dropTarget);
                }
            }
        }

        void AttachDragSource(FrameworkElement dragSource)
        {
            //dragSource.PreviewMouseLeftButtonDown += DragSource_PreviewMouseLeftButtonDown;
            //dragSource.PreviewMouseLeftButtonUp += DragSource_PreviewMouseLeftButtonUp;
            //dragSource.PreviewMouseMove += DragSource_PreviewMouseMove;
        }

        void DetachDragSource(FrameworkElement dragSource)
        {
            dragSource.PreviewMouseLeftButtonDown -= DragSource_PreviewMouseLeftButtonDown;
            dragSource.PreviewMouseLeftButtonUp -= DragSource_PreviewMouseLeftButtonUp;
            dragSource.PreviewMouseMove -= DragSource_PreviewMouseMove;
        }
        
        void AttachDropTarget(FrameworkElement dropTarget)
        {
            dropTarget.PreviewDrop += DropTarget_PreviewDrop;
            dropTarget.PreviewDragEnter += DropTarget_PreviewDragEnter;
            dropTarget.PreviewDragOver += DropTarget_PreviewDragOver;
            dropTarget.PreviewDragLeave += DropTarget_PreviewDragLeave;
        }

        void DetachDropTarget(FrameworkElement dropTarget)
        {
            dropTarget.PreviewDrop -= DropTarget_PreviewDrop;
            dropTarget.PreviewDragEnter -= DropTarget_PreviewDragEnter;
            dropTarget.PreviewDragOver -= DropTarget_PreviewDragOver;
            dropTarget.PreviewDragLeave -= DropTarget_PreviewDragLeave;
        }
        #endregion

        void DragSource_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
		{
            FrameworkElement sourceElement = (FrameworkElement)sender;
            _topWindow = Window.GetWindow(sourceElement);
            _initialMousePosition = e.GetPosition(_topWindow);
            Visual visual = (Visual)e.OriginalSource;
            IDragProcessor dragProcessor = GetDragProcessor(sourceElement);            
            _sourceManager = DragDropManagerFactory.CreateDragSourceHelper(sourceElement, visual, dragProcessor);
		}

		void DragSource_PreviewMouseMove(object sender, MouseEventArgs e)
		{
			if (_sourceManager != null && _sourceManager.CanDrag)
			{
				if (Utilities.IsMovementBigEnough(_initialMousePosition, e.GetPosition(_topWindow)))
				{
                    Point topLeftPosition = _sourceManager.SourceItemContainer.TranslatePoint(new Point(0, 0), _topWindow);
                    _initialMouseOffset = _initialMousePosition - topLeftPosition;

					DataObject data = new DataObject(_format.Name, _sourceManager.DraggedData);

					bool previousAllowDrop = _topWindow.AllowDrop;
                    try
                    {
                        _topWindow.AllowDrop = true;
                        _topWindow.DragEnter += TopWindow_DragEnter;
                        _topWindow.DragOver += TopWindow_DragOver;
                        _topWindow.DragLeave += TopWindow_DragLeave;

                        DragDropEffects effects = DragDrop.DoDragDrop((DependencyObject)sender, data, DragDropEffects.Move);
                    }
                    finally
                    {
                        _topWindow.AllowDrop = previousAllowDrop;
                        _topWindow.DragEnter -= TopWindow_DragEnter;
                        _topWindow.DragOver -= TopWindow_DragOver;
                        _topWindow.DragLeave -= TopWindow_DragLeave;
                    }
                    _sourceManager.RemoveDraggedAdorner();
                    _sourceManager = null;
				}
			}
		}
			
		void DragSource_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
		{
            _sourceManager = null;
		}

        void DropTarget_PreviewDragEnter(object sender, DragEventArgs e)
		{
            Trace.WriteLine("PreviewDragEnter: " + sender);
            DependencyObject obj = (DependencyObject)sender;
            IDropProcessor processor = GetDropProcessor(obj);
            _targetManager = DragDropManagerFactory.CreateDropTargetHelper((DependencyObject)sender, processor);
            object draggedItem = e.Data.GetData(_format.Name);
            if (_targetManager.CanDrop(draggedItem))
            {
                e.Effects = _targetManager.DecideDropTarget((DependencyObject)e.OriginalSource, e.GetPosition);
                if (_sourceManager != null)
                {
                    _sourceManager.EnsureDraggedAdorner(e.GetPosition(_topWindow), _initialMousePosition, _initialMouseOffset);
                    _targetManager.CreateInsertionAdorner();
                }
                e.Handled = true;
            }
            else
            {
                e.Effects = DragDropEffects.None;
            }
		}

		void DropTarget_PreviewDragOver(object sender, DragEventArgs e)
		{
            object draggedItem = e.Data.GetData(_format.Name);
            if (_targetManager.CanDrop(draggedItem))
            {
                e.Effects = _targetManager.DecideDropTarget((DependencyObject)e.OriginalSource, e.GetPosition);
                if (draggedItem != null)
                {
                    _sourceManager.EnsureDraggedAdorner(e.GetPosition(_topWindow), _initialMousePosition, _initialMouseOffset);
                    _targetManager.UpdateInsertionAdornerPosition();
                }
                e.Handled = true;
            }
            else
            {
                e.Effects = DragDropEffects.None;
            }
		}

		void DropTarget_PreviewDrop(object sender, DragEventArgs e)
		{
            if (_sourceManager != null)
            {
                object draggedItem = e.Data.GetData(_format.Name);
                bool isMove = (e.Effects & DragDropEffects.Move) == DragDropEffects.Move;
                int originalIndex = -1;
                if (isMove)
                {
                    originalIndex = _sourceManager.RemoveDraggedItem(draggedItem);
                }
                _targetManager.InsertDraggedItem(draggedItem, _sourceManager, isMove, originalIndex);
                _sourceManager.RemoveDraggedAdorner();
                _targetManager.RemoveInsertionAdorner();
			}
			e.Handled = true;
		}

		void DropTarget_PreviewDragLeave(object sender, DragEventArgs e)
		{
			if (_sourceManager != null)
            {
                object draggedItem = e.Data.GetData(_format.Name);
                _targetManager.RemoveInsertionAdorner();
			}
			e.Handled = true;
		}

		void TopWindow_DragEnter(object sender, DragEventArgs e)
		{
            _sourceManager.EnsureDraggedAdorner(e.GetPosition(_topWindow), _initialMousePosition, _initialMouseOffset);
            e.Effects = DragDropEffects.None;
			e.Handled = true;
		}

		void TopWindow_DragOver(object sender, DragEventArgs e)
		{
            _sourceManager.EnsureDraggedAdorner(e.GetPosition(_topWindow), _initialMousePosition, _initialMouseOffset);
            e.Effects = DragDropEffects.None;
			e.Handled = true;
		}

		void TopWindow_DragLeave(object sender, DragEventArgs e)
		{
            _sourceManager.RemoveDraggedAdorner();
			e.Handled = true;
		}
	}
}
